<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>📈 涨停分析 - 股票选股系统</title>
    
    <!-- CSS样式 -->
    <link rel="stylesheet" href="/static/base.css">
    <link rel="stylesheet" href="/static/components.css">
    <link rel="stylesheet" href="/static/pages.css">
    <link rel="stylesheet" href="/static/utilities.css">
    <link rel="stylesheet" href="/static/css/common.css">
    <link href="/static/lib/css/all.min.css" rel="stylesheet">
    <style>
        .limit-up-card {
            border: 1px solid #e1e5e9;
            border-radius: 10px;
            padding: 12px;
            margin-bottom: 12px;
            background: white;
            transition: all 0.2s ease;
            position: relative;
            overflow: hidden;
        }
        
        .limit-up-card:hover {
            box-shadow: 0 8px 25px rgba(0,0,0,0.15);
            transform: translateY(-3px);
        }
        
        .limit-up-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 8px;
        }
        
        .stock-info {
            flex: 1;
        }
        
        .stock-code { font-size: 16px; font-weight: 700; color: #2c3e50; margin-bottom: 2px; }
        
        .stock-name { font-size: 12px; color: #7f8c8d; margin-bottom: 4px; }
        
        .consecutive-badge { background: linear-gradient(135deg, #e74c3c, #c0392b); color: #fff; padding: 4px 10px; border-radius: 14px; font-size: 11px; font-weight: 600; min-width: 54px; text-align:center; }
        
        .limit-up-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 8px; margin-bottom: 8px; }
        
        .stat-item { text-align: center; padding: 8px; background: #f8f9fa; border-radius: 6px; border-left: 3px solid #3498db; }
        
        .stat-label { font-size: 11px; color: #7f8c8d; margin-bottom: 2px; text-transform: uppercase; }
        
        .stat-value { font-size: 14px; font-weight: 600; color: #2c3e50; }
        
        .price-info { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
        
        .current-price { font-size: 18px; font-weight: 700; color: #e74c3c; }
        
        .price-change { font-size: 12px; color: #7f8c8d; }
        
        .concepts { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
        
        .concept-tag { background: #ecf0f1; color: #34495e; padding: 3px 6px; border-radius: 10px; font-size: 10px; font-weight: 500; }
        
        .market-info { display: flex; justify-content: space-between; align-items: center; font-size: 12px; color: #7f8c8d; }
        
        .market-badge {
            padding: 2px 6px;
            border-radius: 9px;
            font-size: 10px;
            font-weight: 600;
            text-transform: uppercase;
        }
        /* 紧凑行与可展开明细 */
        /* 列表样式已废弃，保留空占位以防旧DOM残留 */
        .compact-row, .details, .limit-up-card { display:none !important; }
        /* 热力图视图 */
        .view-switch { display:flex; gap:8px; margin: 8px 0; align-items:center; }
        .view-switch .btn { padding:6px 10px; border:1px solid #d1d5db; background:#fff; border-radius:6px; cursor:pointer; }
        .view-switch .btn.active { background:#0ea5e9; color:#fff; border-color:#0ea5e9; }
        .heatmap { overflow:auto; border:1px solid #e5e7eb; border-radius:8px; }
        .heatmap table { border-collapse: collapse; width: 100%; min-width: 520px; table-layout: fixed; }
        .heatmap th, .heatmap td { border:1px solid #e5e7eb; padding:6px; font-size:12px; text-align:center; }
        .heatmap th { background:#f9fafb; color:#374151; position:sticky; top:0; }
        .hm-cell { color:#111827; font-weight:600; border-radius:4px; padding:4px 0; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }
        .hm-legend { display:flex; gap:8px; align-items:center; padding:6px 8px; color:#6b7280; font-size:12px; }
        .hm-swatch { width:24px; height:10px; border:1px solid #e5e7eb; border-radius:2px; }
        /* Heatmap-only layout */
        .app-container { max-width: 1600px; margin: 0 auto; padding: 20px; }
        .view-switch, .filters-bar, .summary-stats, #limitUpList, #pagination { display:none !important; }
        #heatmapView { display:block; margin-top: 8px; }
        .heatmap { border:1px solid #e5e7eb; border-radius:8px; overflow:auto; }
        .heatmap table { width: 100%; min-width: 560px; table-layout: fixed; border-collapse: collapse; }
        .heatmap th, .heatmap td { border:1px solid #e5e7eb; padding:6px; text-align:center; font-size:12px; }
        .heatmap th:first-child { position: sticky; left: 0; background: #f3f4f6; z-index: 1; text-align: left; }
        .hm-cell { padding: 4px 0; border-radius: 3px; font-weight: 600; color:#111827; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; }
        .hm-cell:hover { outline: 2px solid rgba(37,99,235,.3); }
        @media (max-width: 768px){ .app-container { padding: 8px; } }
        /* 详情面板 */
        .detail-panel { margin-top: 10px; border:1px solid #e5e7eb; border-radius:8px; background:#fff; overflow:hidden; }
        .detail-header { padding:10px; background:#f9fafb; border-bottom:1px solid #e5e7eb; display:flex; justify-content:space-between; align-items:center; }
        .detail-title { font-size:14px; font-weight:700; color:#111827; }
        .detail-close { border:1px solid #d1d5db; background:#fff; padding:4px 8px; border-radius:6px; cursor:pointer; font-size:12px; }
        .detail-table { width:100%; border-collapse:collapse; }
        .detail-table th, .detail-table td { border-top:1px solid #eef2f7; padding:8px; font-size:12px; text-align:center; }
        .detail-table th { background:#fafafa; color:#374151; }
        .gain-pos { color:#e53935; font-weight:600; }
        .gain-neg { color:#2e7d32; font-weight:600; }
        
        
        @media (max-width: 768px) {
            .limit-up-stats {
                grid-template-columns: repeat(2, 1fr);
            }
            
            .filter-row {
                flex-direction: column;
                align-items: stretch;
            }
            
            .filter-group {
                flex-direction: row;
                justify-content: space-between;
                align-items: center;
            }
            
            .summary-stats {
                grid-template-columns: 1fr;
            }
        }
    </style>
</head>
<body>
    <div class="app-container">
        <!-- 头部 -->
        <div class="header">
            <div class="header-nav">
                <h1><i class="fas fa-chart-line"></i> 涨停分析</h1>
                <div class="header-nav-links">
                    <!-- 导航将由JavaScript动态生成 -->
                </div>
            </div>
            <p>实时涨停板数据分析，助您把握市场热点</p>
        </div>

        <!-- 筛选栏 -->
        <div class="filters-bar">
            <div class="filter-row">
                <div class="filter-group">
                    <label class="filter-label">交易日期</label>
                    <input type="date" id="tradeDate" class="filter-input" value="">
                </div>
                <div class="filter-group">
                    <label class="filter-label">股票代码</label>
                    <input type="text" id="securityId" class="filter-input" placeholder="输入股票代码">
                </div>
                <div class="filter-group">
                    <label class="filter-label">股票名称</label>
                    <input type="text" id="securityName" class="filter-input" placeholder="输入股票名称">
                </div>
                <div class="filter-group">
                    <label class="filter-label">连板次数</label>
                    <select id="consecutiveLimitUp" class="filter-select">
                        <option value="">全部</option>
                        <option value="1">1连板</option>
                        <option value="2">2连板</option>
                        <option value="3">3连板</option>
                        <option value="4">4连板</option>
                        <option value="5">5连板及以上</option>
                    </select>
                </div>
                <div class="filter-group">
                    <label class="filter-label">市场</label>
                    <select id="market" class="filter-select">
                        <option value="">全部</option>
                        <option value="1">上海</option>
                        <option value="2">深圳</option>
                    </select>
                </div>
                <div class="filter-group">
                    <button onclick="searchLimitUp()" class="filter-btn">
                        <i class="fas fa-search"></i> 搜索
                    </button>
                </div>
                <div class="filter-group">
                    <button onclick="resetFilters()" class="filter-btn secondary">
                        <i class="fas fa-redo"></i> 重置
                    </button>
                </div>
            </div>
        </div>

        <!-- 统计摘要 -->
        <div class="summary-stats" id="summaryStats">
            <div class="summary-card">
                <div class="summary-value" id="totalCount">-</div>
                <div class="summary-label">涨停总数</div>
            </div>
            <div class="summary-card">
                <div class="summary-value" id="avgConsecutive">-</div>
                <div class="summary-label">平均连板</div>
            </div>
            <div class="summary-card">
                <div class="summary-value" id="maxConsecutive">-</div>
                <div class="summary-label">最高连板</div>
            </div>
            <div class="summary-card">
                <div class="summary-value" id="totalTurnover">-</div>
                <div class="summary-label">总成交额(亿)</div>
            </div>
        </div>

        <!-- 视图切换 -->
        <div class="view-switch" style="justify-content:flex-start;">
            <span style="font-size:13px; color:#6b7280;">视图：热力图</span>
        </div>

        <!-- 涨停列表（已隐藏，不再展示） -->
        <div id="limitUpList" style="display:none;"></div>

        <!-- 热力图容器 -->
        <div id="heatmapView" class="heatmap" style="display:block;"></div>
        <!-- 详情面板 -->
        <div id="heatmapDetail" class="detail-panel" style="display:none;"></div>

    </div>

    <script>
        // 全局变量
        let currentPage = 1;
        let pageSize = 20;
        let totalPages = 1;
        let totalCount = 0;
        let currentFilters = {};

        // 页面加载完成后初始化
        document.addEventListener('DOMContentLoaded', function() {
            // 设置默认日期为今天
            const today = new Date().toISOString().split('T')[0];
            document.getElementById('tradeDate').value = today;
            
            // 加载数据
            loadLimitUpData();
        });

        // 加载涨停数据
        async function loadLimitUpData() {
            try {
                // 先请求第一页，获知总页数
                const base = { page: 1, limit: pageSize, ...currentFilters };
                const firstResp = await fetch(`/api/limit-up/list?${new URLSearchParams(base).toString()}`);
                const firstJson = await firstResp.json();
                console.log(firstJson);
                if (!firstJson.success) {
                    limitUpList.innerHTML = `<div class=\"error\"><i class=\"fas fa-exclamation-triangle\"></i> ${firstJson.error || '加载失败'}</div>`;
                    return;
                }
                const allItems = normalizeItems(firstJson.data);
                // 识别总页数
                const dataMeta = firstJson.data || {};
                let totalPages = dataMeta.total_pages || dataMeta.pages || dataMeta.totalPages || 1;
                if (!totalPages && typeof dataMeta.total_count === 'number' && typeof dataMeta.page_size === 'number' && dataMeta.page_size > 0) {
                    totalPages = Math.ceil(dataMeta.total_count / dataMeta.page_size);
                }
                // 拉取剩余页
                for (let p = 2; p <= (totalPages || 1); p++) {
                    const resp = await fetch(`/api/limit-up/list?${new URLSearchParams({ ...currentFilters, page: p, limit: pageSize }).toString()}`);
                    const json = await resp.json();
                    if (json && json.success) {
                        allItems.push(...normalizeItems(json.data));
                    }
                }
                // 用全部数据渲染热力图
                renderLimitUpList(allItems);
            } catch (error) {
                limitUpList.innerHTML = `<div class=\"error\"><i class=\"fas fa-exclamation-triangle\"></i> ${error.message}</div>`;
            }
        }

        // 统一规范化后端返回的数据结构
        function normalizeItems(data){
            // 常见结构：{ items: [...] } / { list: [...] } / { results: [...] } / 直接数组
            let items = [];
            if (Array.isArray(data)) items = data;
            else if (Array.isArray(data?.items)) items = data.items;
            else if (Array.isArray(data?.list)) items = data.list;
            else if (Array.isArray(data?.results)) items = data.results;
            else if (Array.isArray(data?.data)) items = data.data; // 兜底
            // 字段规范：确保使用 sector、daily_change、security_id 等键
            return (items || []).map(it => ({
                security_id: it.security_id ?? it.securityId ?? it.code ?? '',
                security_name: it.security_name ?? it.securityName ?? it.name ?? '',
                daily_change: it.daily_change ?? it.dailyChange ?? it.change ?? it.change_percent ?? 0,
                close_price: it.close_price ?? it.close ?? it.price ?? 0,
                consecutive_limit_up: it.consecutive_limit_up ?? it.consecutiveLimitUp ?? it.limit_up_count ?? 0,
                first_limit_up_time: it.first_limit_up_time ?? it.firstLimitUpTime ?? '',
                last_limit_up_time: it.last_limit_up_time ?? it.lastLimitUpTime ?? '',
                hot_concepts: it.hot_concepts ?? it.concepts ?? '',
                sector: it.sector ?? it.industry ?? '未知板块',
                market: it.market ?? it.market_code ?? it.marketCode ?? '',
                trade_date: it.trade_date ?? it.tradeDate ?? it.date ?? '',
                type: it.type ?? it.stock_type ?? it.security_type ?? it.category ?? it.board ?? ''
            }));
        }

        // 渲染列表（当前仅用于驱动热力图数据源）
        function renderLimitUpList(data) {
            const listContainer = document.getElementById('limitUpList');
            if (!listContainer) return;
            const items = Array.isArray(data) ? data : normalizeItems(data);
            if(items.length === 0){
                listContainer.className = 'no-data';
                listContainer.innerHTML = '<i class="fas fa-info-circle"></i> 暂无数据';
            }
            // 仅渲染热力图（列表隐藏）
            renderHeatmap(items);
            const heatView = document.getElementById('heatmapView');
            if (heatView) heatView.style.display = 'block';
        }

        // 生成热力图（行业 x 涨跌幅区间）
        function renderHeatmap(items){
            const container = document.getElementById('heatmapView');
            if (!container) return;
            // 存储数据供详情使用
            window.__limitUpItems = items || [];
            // 解析涨跌幅，兼容带%的字符串
            function parseChange(v){
                if (v === null || v === undefined) return 0;
                const n = Number(String(v).replace(/%/g,'').trim());
                return isNaN(n) ? 0 : n;
            }
            const ranges = [
                { key: '>10%', test: v => v > 10 },
                { key: '5%-10%', test: v => v > 5 && v <= 10 },
                { key: '0%-5%', test: v => v > 0 && v <= 5 },
                { key: '<=0%', test: v => v <= 0 }
            ];
            const industries = Array.from(new Set(items.map(i => i.sector || '未知板块'))).sort();
            const counts = {}; // counts[industry][rangeKey]
            industries.forEach(ind => { counts[ind] = {}; ranges.forEach(r => counts[ind][r.key] = 0); });
            items.forEach(it => {
                const ind = it.sector || '未知板块';
                const v = parseChange(it.daily_change);
                const r = ranges.find(x => x.test(v));
                if (r) counts[ind][r.key] += 1;
            });
            const allCounts = [];
            industries.forEach(ind => ranges.forEach(r => allCounts.push(counts[ind][r.key])));
            const maxCount = Math.max(1, ...allCounts);
            // 颜色映射（浅->深蓝）
            function cellColor(val){
                const t = val / maxCount;
                const start = { r: 219, g: 234, b: 254 }; // #DBEAFE
                const end = { r: 37, g: 99, b: 235 }; // #2563EB
                const r = Math.round(start.r + (end.r - start.r) * t);
                const g = Math.round(start.g + (end.g - start.g) * t);
                const b = Math.round(start.b + (end.b - start.b) * t);
                return `rgb(${r},${g},${b})`;
            }
            let html = '';
            html += '<div class="hm-legend">热力图说明：颜色越深数量越多';
            html += '<span class="hm-swatch" style="background:#DBEAFE"></span><span class="hm-swatch" style="background:#60A5FA"></span><span class="hm-swatch" style="background:#2563EB"></span>';
            html += '</div>';
            html += '<table><thead><tr><th>行业/区间</th>' + ranges.map(r => `<th>${r.key}</th>`).join('') + '</tr></thead><tbody>';
            industries.forEach(ind => {
                html += '<tr>'; html += `<th style="text-align:left">${ind}</th>`;
                ranges.forEach(r => {
                    const val = counts[ind][r.key];
                    const color = cellColor(val);
                    html += `<td title="${ind} · ${r.key}: ${val}"><div class="hm-cell" style="background:${color}" data-industry="${encodeURIComponent(ind)}" data-range="${encodeURIComponent(r.key)}">${val}</div></td>`;
                });
                html += '</tr>';
            });
            html += '</tbody></table>';
            container.innerHTML = html;
            // 绑定点击事件，避免内联参数转义问题
            container.querySelectorAll('.hm-cell').forEach(cell => {
                cell.addEventListener('click', () => {
                    const ind = decodeURIComponent(cell.getAttribute('data-industry')||'');
                    const rng = decodeURIComponent(cell.getAttribute('data-range')||'');
                    showHeatmapDetail(ind, rng);
                });
            });
        }

        // 显示详情：按行业与区间过滤
        function showHeatmapDetail(industry, rangeKey){
            const panel = document.getElementById('heatmapDetail');
            if (!panel) return;
            const items = (window.__limitUpItems || []).filter(it => (it.sector || '未知板块') === industry);
            function toNumber(v){ const n = Number(String(v).replace(/%/g,'').trim()); return isNaN(n)?0:n; }
            function inRange(val){
                const v = toNumber(val);
                if (rangeKey === '>10%') return v > 10;
                if (rangeKey === '5%-10%') return v > 5 && v <= 10;
                if (rangeKey === '0%-5%') return v > 0 && v <= 5;
                return v <= 0; // <=0%
            }
            // 去重：同一代码仅保留最新一条（按 trade_date 字符串比较或首涨时间）
            const latestByCode = new Map();
            items.forEach(it => {
                const key = String(it.security_id||'');
                const prev = latestByCode.get(key);
                const curDate = String(it.trade_date||'');
                if (!prev || String(prev.trade_date||'') < curDate) {
                    latestByCode.set(key, it);
                }
            });
            let rows = Array.from(latestByCode.values()).filter(it => inRange(it.daily_change));
            // 排序：按涨幅降序
            rows.sort((a,b)=> toNumber(b.daily_change) - toNumber(a.daily_change));
            let html = '';
            html += `<div class="detail-header"><div class="detail-title">${industry} · ${rangeKey}（${rows.length}）</div><button class="detail-close" onclick="document.getElementById('heatmapDetail').style.display='none'">关闭</button></div>`;
            if (!rows.length){ panel.innerHTML = html + '<div style="padding:12px; color:#6b7280;">暂无数据</div>'; panel.style.display='block'; return; }
            html += '<div style="overflow:auto;"><table class="detail-table"><thead><tr>'+
                    '<th>代码</th><th>名称</th><th>类型</th><th>涨幅%</th><th>现价</th><th>连板</th><th>首次涨停</th><th>概念</th>'+
                    '</tr></thead><tbody>';
            rows.forEach(it => {
                const change = toNumber(it.daily_change);
                html += `<tr>
                    <td>${it.security_id}</td>
                    <td>${it.security_name || ''}</td>
                    <td>${it.type || '-'}</td>
                    <td class="${change>=0?'gain-pos':'gain-neg'}">${change>=0?'+':''}${change.toFixed(2)}</td>
                    <td>${Number(it.close_price||0).toFixed(2)}</td>
                    <td>${it.consecutive_limit_up || 0}</td>
                    <td>${formatLimitUpTime(it.first_limit_up_time)}</td>
                    <td>${(it.hot_concepts||'').split(',').slice(0,3).join('、') || '-'}</td>
                </tr>`;
            });
            html += '</tbody></table></div>';
            panel.innerHTML = html; panel.style.display = 'block';
            // 滚动到详情区域
            panel.scrollIntoView({ behavior:'smooth', block:'start' });
        }

        // 更新统计摘要
        function updateSummaryStats(data) {
            document.getElementById('totalCount').textContent = data.total_count || 0;
            document.getElementById('avgConsecutive').textContent = (data.avg_consecutive || 0).toFixed(1);
            document.getElementById('maxConsecutive').textContent = data.max_consecutive || 0;
            document.getElementById('totalTurnover').textContent = formatAmount(data.total_turnover);
        }

        // 更新分页
        function updatePagination(data) {
            totalPages = data.total_pages || 1;
            totalCount = data.total_count || 0;
            
            document.getElementById('currentPage').textContent = currentPage;
            document.getElementById('totalPages').textContent = totalPages;
            
            const pagination = document.getElementById('pagination');
            if (totalPages > 1) {
                pagination.style.display = 'flex';
            } else {
                pagination.style.display = 'none';
            }
        }

        // 搜索涨停数据
        function searchLimitUp() {
            currentFilters = {
                trade_date: document.getElementById('tradeDate').value,
                security_id: document.getElementById('securityId').value,
                security_name: document.getElementById('securityName').value,
                consecutive_limit_up: document.getElementById('consecutiveLimitUp').value,
                market: document.getElementById('market').value
            };
            
            currentPage = 1;
            loadLimitUpData();
        }

        // 重置筛选条件
        function resetFilters() {
            document.getElementById('tradeDate').value = new Date().toISOString().split('T')[0];
            document.getElementById('securityId').value = '';
            document.getElementById('securityName').value = '';
            document.getElementById('consecutiveLimitUp').value = '';
            document.getElementById('market').value = '';
            
            currentFilters = {};
            currentPage = 1;
            loadLimitUpData();
        }

        // 上一页
        function prevPage() {
            if (currentPage > 1) {
                currentPage--;
                loadLimitUpData();
            }
        }

        // 下一页
        function nextPage() {
            if (currentPage < totalPages) {
                currentPage++;
                loadLimitUpData();
            }
        }

        // 已移除切换逻辑，默认展示热力图

    </script>
    
    <!-- 公共JavaScript库 -->
    <script src="/static/js/common.js"></script>
    <script src="/static/js/navigation.js"></script>
</body>
</html>
